﻿
namespace FolderSync
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text.RegularExpressions;
    using System.IO;
    using System.Security.Permissions;
    using System.Threading;
    using System.Diagnostics;
    using System.Configuration;
    public class Arguments : System.Collections.Generic.Dictionary<string, string>
    {
        public bool HasFlag(string key)
        {
            if (!this.ContainsKey(key)) return false;
            else switch (this[key])
                {
                    case "yes":
                    case "true":
                        return true;
                    default:
                        return false;

                }
        }
        public bool ExecuteIfContainsKey(string key, Func<string, bool> λ)
        {
            if (this.ContainsKey(key))
            {
                return λ(this[key]);
            }
            else
            {
                return false;
            }
        }
    }
    public static class Bootstrapper
    {
        static string WatchFolderName { get; set; }
        static string DestinationFolderName { get; set; }
        static Dictionary<string, Regex> re = new Dictionary<string, Regex>(4);
        static string LogTag = string.Empty;

        static int Main()
        {
            var args = ParseArgs();
            var configset = new ConfigurationSet(System.Environment.GetCommandLineArgs());

            if (configset.Destination != string.Empty) configset.Destination.EnsureFolderExists(true);
            var notifier = new ConsoleNotifier() { LogFile = configset.LogFileinfo(dated: true), LogTag = configset.Logtag, Level = NotifierLevel.Message, ToConsole = true };

            if (args.ContainsKey("save"))
            {
                configset.Save(args["save"]);
                notifier.Message(ConfigurationSet.Display(args["save"]));
                notifier.Message("Configuration saved");
                return 0;
            }
            if (args.ContainsKey("del"))
            {
                notifier.Message(ConfigurationSet.Display(args["del"]));
                ConfigurationSet.Delete(args["del"]);
                notifier.Message("Configuration set deleted");
                return 0;
            }
            if (args.ContainsKey("edit"))
            {
                configset = ConfigurationSet.GetConfigurationSet(args["edit"]) + configset;
                configset.Save(args["edit"]);
                notifier.Message(ConfigurationSet.Display(args["edit"]));
                notifier.Message("Configuration edited");
                return 0;
            }

            if (args.ContainsKey("disp"))
            {
                notifier.Message(ConfigurationSet.Display(args["disp"]));
                notifier.Message("Configuration set displayed");
                return 0;
            }

            ConfigurationSet[] configsets;
            if (args.ContainsKey("run"))
            {
                configsets = ConfigurationSet.GetConfigurationSets(args["run"]);
                configset.Logfile = ConfigurationSet.GetLogFileName(args["run"]);
            }
            else configsets = configset;

            // reinit notifier which parameters may be affected by the newly loaded config (typically log file)
            notifier = new ConsoleNotifier() { LogFile = configset.LogFileinfo(dated: true), LogTag = configset.Logtag, Level = NotifierLevel.Message, ToConsole = true };

            notifier.Message("FolderSync starting with Options:");
            notifier.Message(configsets.SelectMany(cfg => cfg.ToStrings()));
            // If a directory is not specified, exit program.
            if (configsets
                    .Where(cfg => cfg.Name.ToLower() != "default")
                    .Any(cfg => cfg.Source == string.Empty))
            {
                // Display the proper way to call the program.
                DisplaySyntaxHelp();
                return 1;
            }
            configsets
                .Where(cfg => cfg.Name.ToLower() != "default")
                .Select(cfg => new { src = cfg.Source, folder = cfg.Source.EnsureFolderExists() })
                .Where(src => string.IsNullOrWhiteSpace(src.folder))
                .All(src => { throw new Exception(string.Format("Folder to watch does not exist :«{0}»", src.src)); });
            try
            {
                var watcher = new Watcher()
                    {
                        ConfigSets = configsets.Where(cfg => cfg.Name.ToLower() != "default").ToArray(),
                        NoRun = configset.NoRun,
                        Notifier = notifier,
                    };
                watcher.StatusChanged += new Watcher.StatusChangedHandler(watcher_StatusChanged);
                var watcherThread = new Thread(watcher.Run);
                watcherThread.Name = "Watcher";
                watcherThread.Start();
                while (watcherThread.IsAlive)
                {
                    try
                    {
                        switch (Console.ReadKey(true).KeyChar.ToString().ToLower())
                        {
                            case "q":
                                watcher.Stop();
                                if (watcherThread.IsAlive) watcherThread.Join();
                                break;
                            case "l": watcher.Syncer.List(onDebugOnly: false); break;
                            case "d": notifier.Message(
                                    !configset.NoRun
                                    ? "Syncer is active"
                                    : watcher.Syncer.Dequeue()
                                    ? "Dequeue 1 event"
                                    : "Queue is empty"
                                ); break;
                            case "s":
                            case "t":
                                notifier.Message("FolderSync running with Options:");
                                notifier.Message(configsets.SelectMany(cfg => cfg.ToStrings()));
                                notifier.Message(watcher.Syncer.Statistics.ToString(verbose: true));
                                notifier.Message(watcher.Syncer.Status);
                                notifier.Message(watcher.MirrorSerializer.Status);
                                break;
                            default: break;
                        }
                    }
                    catch (Exception e)
                    {
                        notifier.Error(e.Message + "\r\n" + e.StackTrace);
                    }
                }
                return 0;
            }
            catch (Exception e)
            {
                DisplayHelp(e.Message);
                return -1;
            }
        }

        static void watcher_StatusChanged(object sender, string status)
        {
            Console.Title = System.AppDomain.CurrentDomain.FriendlyName + " - " + sender.GetType().Name + ":" + status;
        }
        static void DisplayHelp(string msg = null)
        {
            Console.Out.WriteLine(string.Empty);
            if (!string.IsNullOrWhiteSpace(msg))
                Console.Out.WriteLine("*********************************\r\n{0}\r\n*********************************", msg);
            DisplaySyntaxHelp();
        }
        static void DisplaySyntaxHelp()
        {
            new string[]{
                "Usage is",
                "FolderSync [/src source [/dest destination] [/rd regex] [/rf regex]  [/xd regex] [/xf regex] [/add] [/mir] [/log logfilename] [/logtag logtag]]...  [/save setname]",
                "or FolderSync [/run setname|All] | [/del setname|All] | [/disp setname|All]",
                "where",
                "\t/src\t: indicates the root folder to watch (FolderSync by default recurses to the whole tree)",
                "\t/dest\t: optional, indicates the destination to which the sources sholud be synchronized.",
                "\t\t  If no destination is defined, FolderSync will only whatch and report changes.",
                "\t/rd\t: optional, a regular expression to determine which folders will be included",
                "\t/rf\t: optional, a regular expression to determine which files will be included",
                "\t/xd\t: optional, a regular expression to determine which folders will be excluded",
                "\t/xf\t: optional, a regular expression to determine which files will be excluded",
                "Note: the subfolders of an excluded folder are excluded, the subfolders of an included folder are included unless explicitely excluded.",
                "\t/merge\t: optional, indicates that source folder will be merged into destination prior to watch.",
                "\t/mir\t: optional, indicates that source folder will be mirrored to destination prior to watch (destructive mirroring).",
                "\t/retry\t: optional, default 5, indicates the delay (in seconds) after which a file operation is retried in case of IO error",
                "\t/log\t: optional, a log file to which all messages are echoed.",
                "\t/logtag\t: optional, a text tag added to each log entry.",
                "\t/save\t: optional: saves the parameter set with given set name",
                "\t/del\t: optional: Deletes saves the parameter set with given set name",
                "Or",
                "\t/run setname|All\t: run FolderSync with a previously saved set of parameters, or all previously saved sets of parameters",
                "\t/del setname|All\t: Delete a previously saved set of parameters, or all previously saved sets of parameters",
                "\t/disp setname|All\t: Displays a previously saved set of parameters, or all previously saved sets of parameters",
            }.All(s => { Console.WriteLine(s); return true; });
        }
        static void SaveConfig(Arguments args)
        {
            var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            config.AppSettings.Settings.Clear();
            var setName = args.ContainsKey("save") && args["save"] != "yes" && args["save"] != "«true»"
                            ? "_" + args["save"]
                            : "";
            args.Where(w => w.Value != "«false»" && w.Value != "no").All(w => { config.AppSettings.Settings.Add(w.Key + setName, w.Value); return true; });
            config.Save();
        }
        static Arguments ParseArgs()
        {
            var d = new Arguments();
            var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            foreach (var k in config.AppSettings.Settings.AllKeys) d.Add(k, config.AppSettings.Settings[k].Value);
            string key = null;
            foreach (string s in System.Environment.GetCommandLineArgs())
            {
                if (s.StartsWith("-") || s.StartsWith("/"))
                {
                    key = s.Substring(1).ToLower();
                    d[key] = "yes";
                }
                else
                {
                    if (key == null) key = "";
                    d[key] = s;
                }
            }
            return d;
        }
    }

}